﻿// Copyright (c) Pomelo Foundation. All rights reserved.
// Licensed under the MIT. See LICENSE in the project root for license information.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text.RegularExpressions;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Query.Expressions;
using Microsoft.EntityFrameworkCore.Query.Expressions.Internal;
using Microsoft.EntityFrameworkCore.Query.ExpressionVisitors;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Storage.Internal;
using Microsoft.EntityFrameworkCore.Utilities;
using Pomelo.EntityFrameworkCore.MyCat.Query.Expressions.Internal;
using Remotion.Linq.Clauses;

// ReSharper disable once CheckNamespace
namespace Microsoft.EntityFrameworkCore.Query.Sql.Internal
{
    public class MyCatQuerySqlGenerator : DefaultQuerySqlGenerator
    {
      
        protected override string TypedTrueLiteral => "TRUE";
        protected override string TypedFalseLiteral => "FALSE";

        public MyCatQuerySqlGenerator(
            [NotNull] QuerySqlGeneratorDependencies dependencies,
            [NotNull] SelectExpression selectExpression,
            bool rowNumberPagingEnabled)
            : base(dependencies, selectExpression)
        {
            //if (rowNumberPagingEnabled)
            //{
            //    var rowNumberPagingExpressionVisitor = new RowNumberPagingExpressionVisitor();
            //    rowNumberPagingExpressionVisitor.Visit(selectExpression);
            //}
        }
        protected override void GenerateTop([NotNull]SelectExpression selectExpression)
        {
           
        }

        public override Expression VisitTable(TableExpression tableExpression)
        {
            Check.NotNull(tableExpression, nameof(tableExpression));

            Sql.Append(SqlGenerator.DelimitIdentifier(tableExpression.Table))
                .Append(" AS ")
                .Append(SqlGenerator.DelimitIdentifier(tableExpression.Alias));

            return tableExpression;
        }


        protected override void GenerateLimitOffset([NotNull] SelectExpression selectExpression)
        {
            Check.NotNull(selectExpression, nameof(selectExpression));

            if (selectExpression.Limit != null)
            {
                Sql.AppendLine().Append("LIMIT ");
                Visit(selectExpression.Limit);
            }

            if (selectExpression.Offset != null)
            {
                if (selectExpression.Limit == null)
                {
                    // if we want to use Skip() without Take() we have to define the upper limit of LIMIT 
                    Sql.AppendLine().Append("LIMIT ").Append(18446744073709551610);
                }
                Sql.Append(' ');
                Sql.Append("OFFSET ");
                Visit(selectExpression.Offset);
            }
        }

      
        protected override Expression VisitBinary(BinaryExpression binaryExpression)
        {
            if (binaryExpression.NodeType == ExpressionType.Add &&
                binaryExpression.Left.Type == typeof (string) &&
                binaryExpression.Right.Type == typeof (string))
            {
                Sql.Append("CONCAT(");
                //var exp = base.VisitBinary(binaryExpression);
                Visit(binaryExpression.Left);
                Sql.Append(",");
                var exp = Visit(binaryExpression.Right);
                Sql.Append(")");
                return exp;
            }
            
            var expr = base.VisitBinary(binaryExpression);
            
            return expr;
        }
        
        public Expression VisitRegexMatch([NotNull] RegexMatchExpression regexMatchExpression)
        {
            Check.NotNull(regexMatchExpression, nameof(regexMatchExpression));
            var options = regexMatchExpression.Options;

            Visit(regexMatchExpression.Match);
            Sql.Append(" ~ ");

            // PG regexps are singleline by default
            if (options == RegexOptions.Singleline)
            {
                Visit(regexMatchExpression.Pattern);
                return regexMatchExpression;
            }

            Sql.Append("('(?");
            if (options.HasFlag(RegexOptions.IgnoreCase)) {
                Sql.Append('i');
            }

            if (options.HasFlag(RegexOptions.Multiline)) {
                Sql.Append('n');
            }
            else if (!options.HasFlag(RegexOptions.Singleline)) {
                Sql.Append('p');
            }

            if (options.HasFlag(RegexOptions.IgnorePatternWhitespace))
            {
                Sql.Append('x');
            }

            Sql.Append(")' || ");
            Visit(regexMatchExpression.Pattern);
            Sql.Append(')');
            return regexMatchExpression;
        }

        public Expression VisitAtTimeZone([NotNull] AtTimeZoneExpression atTimeZoneExpression)
        {
            Check.NotNull(atTimeZoneExpression, nameof(atTimeZoneExpression));

            //Visit(atTimeZoneExpression.TimestampExpression);

            Sql.Append("UTC_DATE()");
            return atTimeZoneExpression;
        }

        private class RowNumberPagingExpressionVisitor : ExpressionVisitorBase
        {
            private const string RowNumberColumnName = "__RowNumber__";
            private int _counter;

            public override Expression Visit(Expression expression)
            {
                return expression is ExistsExpression existsExpression
                    ? VisitExistExpression(existsExpression)
                    : expression is SelectExpression selectExpression
                    ? VisitSelectExpression(selectExpression)
                    : base.Visit(expression);
            }

            private static bool RequiresRowNumberPaging(SelectExpression selectExpression)
                => selectExpression.Offset != null
                   && !selectExpression.Projection.Any(p => p is RowNumberExpression);

            private Expression VisitSelectExpression(SelectExpression selectExpression)
            {
                base.Visit(selectExpression);

                if (!RequiresRowNumberPaging(selectExpression))
                {
                    return selectExpression;
                }

                var subQuery = selectExpression.PushDownSubquery();
                if (subQuery.Projection.Count > 0)
                {
                    selectExpression.ExplodeStarProjection();
                }

                if (subQuery.OrderBy.Count == 0)
                {
                    subQuery.AddToOrderBy(
                        new Ordering(new SqlFunctionExpression("@@RowCount", typeof(int)), OrderingDirection.Asc));
                }

                var innerRowNumberExpression = new AliasExpression(
                    RowNumberColumnName + (_counter != 0 ? $"{_counter}" : ""),
                    new RowNumberExpression(
                        subQuery.OrderBy
                            .Select(
                                o => new Ordering(
                                    o.Expression is AliasExpression ae ? ae.Expression : o.Expression,
                                    o.OrderingDirection))
                            .ToList()));

                _counter++;

                subQuery.ClearOrderBy();
                subQuery.AddToProjection(innerRowNumberExpression, resetProjectStar: false);

                var rowNumberReferenceExpression = new ColumnReferenceExpression(innerRowNumberExpression, subQuery);

                var offset = subQuery.Offset ?? Expression.Constant(0);

                if (subQuery.Offset != null)
                {
                    selectExpression.AddToPredicate(
                        Expression.GreaterThan(
                            rowNumberReferenceExpression,
                            ApplyConversion(offset, rowNumberReferenceExpression.Type)));

                    subQuery.Offset = null;
                }

                if (subQuery.Limit != null)
                {
                    var constantValue = (subQuery.Limit as ConstantExpression)?.Value;
                    var offsetValue = (offset as ConstantExpression)?.Value;

                    var limitExpression
                        = constantValue != null
                          && offsetValue != null
                            ? (Expression)Expression.Constant((int)offsetValue + (int)constantValue)
                            : Expression.Add(offset, subQuery.Limit);

                    selectExpression.AddToPredicate(
                        Expression.LessThanOrEqual(
                            rowNumberReferenceExpression,
                            ApplyConversion(limitExpression, rowNumberReferenceExpression.Type)));

                    subQuery.Limit = null;
                }

                return selectExpression;
            }

            private static Expression ApplyConversion(Expression expression, Type type)
            {
                return expression.Type != type ? Expression.Convert(expression, type) : expression;
            }

            private Expression VisitExistExpression(ExistsExpression existsExpression)
            {
                var newSubquery = (SelectExpression)Visit(existsExpression.Subquery);

                if (newSubquery.Limit == null
                    && newSubquery.Offset == null)
                {
                    newSubquery.ClearOrderBy();
                }

                return new ExistsExpression(newSubquery);
            }
        }
    }
}
